/*
 * Copyright (c) 2022 The red-star Project
 *
 * Licensed under the Apache License, version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at:
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.inyourcode.core.monitor;

import com.alibaba.fastjson.JSONObject;
import com.inyourcode.core.GlobalConstants;
import com.inyourcode.core.cluster.ClusterNodeConf;
import com.inyourcode.core.cluster.api.ClusterConst;
import com.inyourcode.core.db.redis.RedisConfig;
import com.inyourcode.core.monitor.api.AbstractMonitorOpt;
import com.inyourcode.core.monitor.iml.MonitorOpt4Help;
import com.inyourcode.core.monitor.iml.MonitorOpt4NodeInfo;
import com.inyourcode.core.monitor.iml.MonitorOpt4NodeName;
import com.inyourcode.core.monitor.iml.MonitorOpt4Opt;
import com.inyourcode.core.monitor.iml.MonitorOpt4Quit;
import com.inyourcode.core.util.StackTraceUtil;
import io.netty.channel.Channel;
import org.apache.commons.cli.CommandLine;

import org.apache.commons.cli.Option;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * @author JackLei
 */
@Component
public class MonitorService {
    private static final Logger LOGGER = LoggerFactory.getLogger(MonitorService.class);
    private static MonitorService instance;
    private static final Map<String, AbstractMonitorOpt> OPT_MAP = new HashMap<>();
    private static final Set<AbstractMonitorOpt> OPT_SET = new HashSet<>();
    @Qualifier(RedisConfig.STRING_REDIS_TEMPLATE)
    @Autowired
    private RedisTemplate redisTemplate;

    private MonitorService() {
        instance = this;
    }

    public void addMonitorOpt(AbstractMonitorOpt opt) {
        OPT_SET.add(opt);
    }

    public static MonitorService getInstance() {
        return instance;
    }

    public AbstractMonitorOpt getMonitorOpt(String opt) {
        return OPT_MAP.get(opt);
    }

    public void initOpt() {
        addMonitorOpt(new MonitorOpt4Help());
        addMonitorOpt(new MonitorOpt4NodeInfo());
        addMonitorOpt(new MonitorOpt4Opt());
        addMonitorOpt(new MonitorOpt4NodeName());
        addMonitorOpt(new MonitorOpt4Quit());


        for (AbstractMonitorOpt monitorCommand : OPT_SET) {
            monitorCommand.initOptions();
            OPT_MAP.put(monitorCommand.opt(), monitorCommand);
        }
    }

    public String queryAllNode() {
        StringBuilder buf = new StringBuilder();
        buf.append("-- node Info ------------------------------------------------------------------------" + GlobalConstants.NEWLINE);
        buf.append(String.format("%1$-10s %2$-10s %3$-30s %4$-10s %5$-30s", "nodeId", "nodeType", "ip", "load", "tickTime"))
                .append(GlobalConstants.NEWLINE);

        Map<String, String> clusterDataFromRedis = redisTemplate.opsForHash().entries(ClusterConst.KEY_CLUSTER_DATA);
        if (CollectionUtils.isEmpty(clusterDataFromRedis)) {
            buf.append(GlobalConstants.NEWLINE);
            return buf.toString();
        }

        for (Map.Entry<String, String> entry : clusterDataFromRedis.entrySet()) {
            String value = entry.getValue();
            ClusterNodeConf nodeFromDB = JSONObject.parseObject(value, ClusterNodeConf.class);
            buf.append(String.format("%1$-10s %2$-10s %3$-30s %4$-10s %5$-30s",
                    nodeFromDB.uniqueKey(),
                    nodeFromDB.getNodeType(),
                    nodeFromDB.getClusterIp(),
                    nodeFromDB.getCurrentLoad(),
                    new Date(nodeFromDB.getLastActiveTimeMillis()).toLocaleString()))
                    .append(GlobalConstants.NEWLINE);
        }

        return buf.toString();
    }

    public String queryAllNodeMetrics(String metricsKeyPrefix) {
        StringBuilder buf = new StringBuilder();
        buf.append("-- Request Process Time  ------------------------------------------------------------------------" + GlobalConstants.NEWLINE);

        Map<String, String> clusterDataFromRedis = redisTemplate.opsForHash().entries(ClusterConst.KEY_CLUSTER_DATA);
        if (CollectionUtils.isEmpty(clusterDataFromRedis)) {
            buf.append(GlobalConstants.NEWLINE);
            return buf.toString();
        }

        for (Map.Entry<String, String> entry : clusterDataFromRedis.entrySet()) {
            String value = entry.getValue();
            ClusterNodeConf nodeFromDB = JSONObject.parseObject(value, ClusterNodeConf.class);
            String requestKey = MetricsConstants.NAME_NODE + nodeFromDB.uniqueKey();
            Map<String, String> entries = redisTemplate.opsForHash().entries(requestKey);
            entries.forEach((metricsKey, metricsVal) -> {
                if (!metricsKey.contains(metricsKeyPrefix)) {
                    return;
                }

                buf.append(metricsVal);
            });
        }

        return buf.toString();
    }

    public void processCMD(Channel ch, String[] body) {

        try {
            CommandLine commandLine = AbstractMonitorOpt.parse(body);
            Option[] options = commandLine.getOptions();
            if (options == null || options.length == 0) {
                ch.writeAndFlush( "invalid opt");
                return;
            }

            for (Option option : options) {
                AbstractMonitorOpt monitorOpt = getMonitorOpt(option.getOpt());
                monitorOpt.process(ch, commandLine);
            }

        }catch (Exception ex) {
            LOGGER.error("process cmd error, {}", StackTraceUtil.stackTrace(ex));
            ch.writeAndFlush(GlobalConstants.NEWLINE + "Command execution failure" + GlobalConstants.NEWLINE);
        }
    }

}
